/* Turska Framework/UI library (Turs2)
 * Copyright 2003-2008 Jetro Lauha
 * All rights reserved.
 * Web: http://iki.fi/jetro/turska/
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of EITHER:
 *   (1) The GNU Lesser General Public License as published by the Free
 *       Software Foundation; either version 2.1 of the License, or (at
 *       your option) any later version. The text of the GNU Lesser
 *       General Public License is included with this library in the
 *       file LICENSE-LGPL.txt.
 *   (2) The BSD-style license that is included with this library in
 *       the file LICENSE-BSD.txt.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the files
 * LICENSE-LGPL.txt and LICENSE-BSD.txt for more details.
 *
 * $Id: App.cpp 73 2008-07-07 19:06:57Z jlauha $
 * $Revision: 73 $
 */

#include <time.h>
#ifdef __APPLE__
#include <CoreFoundation/CoreFoundation.h>
#endif // __APPLE__
#include "main.h"


#define WINDOW_CAPTION_STR "Turs2 Framework SDL/OpenGL"

#define BLURMIX(a,b,n) ((a) * ((n) - 1) + (b)) / (n)


App * App::mSingleton = NULL;


App::App() :
    mAlive(true),
    mTime(0),
    mStartTime(0),
    mNextUpdateTime(0),
    mCurrentAppStateHandler(NULL),
    mMouseX(0), mMouseY(0),
    mGraphics(NULL),
    mScreen(NULL)
{
}


App::~App()
{
}    


void App::setInstance(App *singleton)
{
    assert(mSingleton == NULL);
    mSingleton = singleton;
}


bool App::init()
{
    setInstance(this);

    mScreen = SDL_GetVideoSurface();
    assert(mScreen != NULL);
    if (mScreen == NULL)
        return false;

    mGraphics = new GraphicsSDLOpenGL;

    resetTime();
    srand((unsigned int)time(NULL));

    return true;
}


void App::run()
{
#ifdef __APPLE__
    // enable vsync
    long param = 1;
    CGLSetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &param);
#endif // __APPLE__

    // Warp mouse cursor position to middle of window/screen
    mMouseX = mPreviousMouseX = getScreenWidth() / 2;
    mMouseY = mPreviousMouseY = getScreenHeight() / 2;
    SDL_WarpMouse(mMouseX, mMouseY);

    SDL_WM_SetCaption(getWindowCaption(), "");

    while (isAlive())
    {
        const UI32 frameUpdateStart = SDL_GetTicks();

        SDL_Event event;
        while (SDL_PollEvent(&event))
        {
            switch (event.type)
            {
            case SDL_KEYDOWN:
                if (mCurrentAppStateHandler)
                    mCurrentAppStateHandler->onKeyDown(event.key.keysym);
                break;

            case SDL_KEYUP:
                if (mCurrentAppStateHandler)
                    mCurrentAppStateHandler->onKeyUp(event.key.keysym);
                break;

            case SDL_MOUSEMOTION:
                mMouseX = event.motion.x;
                mMouseY = event.motion.y;
                if (mCurrentAppStateHandler)
                    mCurrentAppStateHandler->onMouseMotion(event.motion);
                break;

            case SDL_MOUSEBUTTONUP:
                if (mCurrentAppStateHandler != NULL)
                    mCurrentAppStateHandler->onMouseUp(event.button);
                break;
                
            case SDL_MOUSEBUTTONDOWN:
                if (mCurrentAppStateHandler != NULL)
                {
                    if (event.button.button == 4)
                        mCurrentAppStateHandler->onMouseWheel(-1);
                    else if (event.button.button == 5)
                        mCurrentAppStateHandler->onMouseWheel(1);
                    else
                        mCurrentAppStateHandler->onMouseDown(event.button);
                }
                break;

            case SDL_ACTIVEEVENT:
                if (mCurrentAppStateHandler != NULL &&
                    event.active.state == SDL_APPINPUTFOCUS)
                {
                    if (event.active.gain == 0)
                        mCurrentAppStateHandler->onFocusLost(event.active);
                    else if (event.active.gain == 1)
                        mCurrentAppStateHandler->onFocusGain(event.active);
                }
                break;

            case SDL_QUIT:
                setAlive(false);
                break;
            }
        }

        bool updated = false;
        UI32 time = frameUpdateStart - mStartTime;
        mTime = BLURMIX(mTime, time, 4);

        updated |= update();

        if (updated)
        {
            if (mCurrentAppStateHandler != NULL)
                mCurrentAppStateHandler->render(mScreen);
            render();
            glFlush();
            SDL_GL_SwapBuffers();

            GLenum error = glGetError();
            if (error != GL_NO_ERROR)
                gErrorMessage("GL Error", "0x%08x (%d) %s",
                              error, error, gluErrorString(error));

            mPreviousMouseX = mMouseX;
            mPreviousMouseY = mMouseY;
        }

        UI32 frameUpdateEnd = SDL_GetTicks();
        if (frameUpdateStart == frameUpdateEnd)
            SDL_Delay(2);
        else
            SDL_Delay(0); // yield
    }
}


bool App::update()
{
    if (mTime < mNextUpdateTime)
        return false;

    if (mCurrentAppStateHandler == NULL)
    {
        mNextUpdateTime = mTime;
        return false;
    }

    // TODO: Update this so that false is returned if the current
    //       state handler returned false for each iteration?
    while (mNextUpdateTime < mTime)
    {
        mNextUpdateTime += 1000.0 / mCurrentAppStateHandler->getUpdateFPS();
        
        mCurrentAppStateHandler->update((UI32)mNextUpdateTime);
    }

    return true;
}


void App::resetTime()
{
    mNextUpdateTime = 0;
    mStartTime = SDL_GetTicks();
    mTime = 0;
}


void App::setAppStateHandler(AppState *state)
{
    if (mCurrentAppStateHandler != NULL)
        mCurrentAppStateHandler->onDeactivate();

    mCurrentAppStateHandler = state;
    if (mCurrentAppStateHandler != NULL)
        mCurrentAppStateHandler->onActivate(0);

    resetTime();
}


C8 * App::getWindowCaption()
{
    return WINDOW_CAPTION_STR;
}
